/**
 *  @file Semaphore.cpp
 */
#if defined(WIN32)
#   include <windows.h>
#elif defined(linux)
#   include <fcntl.h>     // for O_* constants
#   include <sys/stat.h>  // for mode constants
#   include <semaphore.h> // POSIX semaphore
#   include <errno.h>
#endif
#include "../common/CppflibMain.h"
#include "../common/CppMemPool.h"
#include "../common/ObjPtrT.h"
#include "../text/CFString.h"
#include "Semaphore.h"
#include "SemaphoreErrException.h"
#include "SemaphoreOverflowException.h"

namespace cppflib
{

namespace threading
{

/**
 *  Default ctor is private, not allowed to be used
 */
Semaphore::Semaphore(void)
{
   this->pSem = NULL;
   this->maxCount = 0;
   this->isFirstInst = true;
   this->pName = NULL;
}

/**
 *  Create an unnamed semaphore (for multi threaded single process)
 */
Semaphore::Semaphore(u32_t maxCount) : pName(NULL), pSem(NULL)
{
   
#if defined(WIN32)
   HANDLE handle = ::CreateSemaphore(NULL, 0, (LONG)maxCount, NULL);
#elif defined(linux)
   sem_t * handle = static_cast<sem_t*>(::MRP_Calloc(1, sizeof(sem_t)));
   // pshared -> 0, share among threads
   sem_init(handle, 0, 0);
#endif

   this->pSem = static_cast<ptrany_t>(handle);
   this->maxCount = maxCount;
   this->isFirstInst = true;   // always true for an unnamed one
}

/**
 *  Create a named semaphore (mainly for inter-process communication)
 *  It can also be used to open an already existing semaphore.
 */
Semaphore::Semaphore(pcwstr_t name, u32_t maxCount) : pName(NULL), pSem(NULL)
{
   this->pName = new CFString(name);
   cppmempool::Retain(this->pName);
   int errCode = 0;

#if defined(WIN32)
   HANDLE handle = ::CreateSemaphore(NULL, 0, static_cast<LONG>(maxCount), name);
   errCode = cppflib::GetLastError();
   this->isFirstInst = (errCode == ERROR_ALREADY_EXISTS) ? false : true;
#elif defined(linux)

#define ACCESSMODE (S_IRUSR|S_IWUSR)
//#define ACCESSMODE 0644

   ObjPtrT<char> pUtf8Str(this->pName->ToUtf8String());
   sem_t * handle = sem_open(pUtf8Str, O_CREAT|O_EXCL, ACCESSMODE, 0);
   errCode = cppflib::GetLastError();
   this->isFirstInst = (errCode == EEXIST) ? false : true;
   if (handle == SEM_FAILED && errCode == EEXIST)
      handle = sem_open(pUtf8Str, O_CREAT, ACCESSMODE, 0);  // get again without O_EXCL
   
   if (handle == SEM_FAILED)  
      handle = static_cast<sem_t*>(NULL);

#endif

   if (!handle) {
      THROW_CTOR_EXCEP_CODE(SemaphoreErrException, _S("Create named semaphore error"), errCode);
   }
   else {
      this->pSem = static_cast<ptrany_t>(handle);
      this->maxCount = maxCount;
   }
}

Semaphore::~Semaphore(void)
{
   Close();
}

void Semaphore::ReleaseBeforeThrowing()
{
   if (this->pSem) {

#if defined(WIN32)
      ::CloseHandle(static_cast<HANDLE>(this->pSem));
#elif defined(linux)
      /* 
       * Named and unnamed semaphores are deleted differently
       */
      if (!this->pName) {
         // unnamed 
         sem_destroy(static_cast<sem_t*>(this->pSem));    
         cppmempool::Release(this->pSem);
      }
      else {
         // named
         sem_close(static_cast<sem_t*>(this->pSem));     // close it first
         ObjPtrT<char> pUtf8Str(this->pName->ToUtf8String());
         sem_unlink(pUtf8Str);                  // unlink it but only valid when all processes/threads has closed it
      }
#endif

      this->pSem = NULL;      
   }

   if (this->pName) {
      cppmempool::Release(this->pName);
      this->pName = NULL;
   }
}

/**
 *  @return true if this semaphore is the first instance in this system
 */
bool Semaphore::IsFirstInstance(void)
{
   return this->isFirstInst;
}


/**
 *  Wait until semaphore count > 0
 */
void Semaphore::Wait(void)
{
#if defined(WIN32)
   ::WaitForSingleObject(static_cast<HANDLE>(this->pSem), INFINITE);
#elif defined(linux)
   sem_wait(static_cast<sem_t*>(this->pSem));
#endif

}

/**
 *  Wait with a timeout value
 *
 *  @param [in] timeout -- timeout value in milliseconds
 */
bool Semaphore::Wait(int timeout)
{
   bool ret = true;
#if defined(WIN32)
   if (::WaitForSingleObject(static_cast<HANDLE>(this->pSem), timeout) != WAIT_OBJECT_0)
      ret = false;
#elif defined(linux)
   struct timespec ts = { 
                          timeout / 1000,             // seconds
                          (timeout % 1000) * 1000000  // nano seconds
                        };
   if (sem_timedwait(static_cast<sem_t*>(this->pSem), &ts) != 0)
      ret = false;
#endif

   return ret;
}

/**
 *  Increment semaphore count by 1
 */
bool Semaphore::Post(void)
{
#if defined(WIN32)
   int prevCount = 0;
   if (!::ReleaseSemaphore(static_cast<HANDLE>(this->pSem), 1, reinterpret_cast<LPLONG>(&prevCount))) {
      if (prevCount == this->maxCount) 
         throw SemaphoreOverflowException(cppflib::GetLastError());
   }
#elif defined(linux)
   if (sem_post(static_cast<sem_t*>(this->pSem)) != 0) {
      int errCode = cppflib::GetLastError();
      if (errCode == EOVERFLOW)
         throw SemaphoreOverflowException(errCode);
   }
#endif

   return true;
}

/**
 *  Close/destroy semaphore
 */
void Semaphore::Close(void)
{
   ReleaseBeforeThrowing();
}

} // end of namespace threading


} // end of namespace cppflib

